/*
 * Created on 17 Sep 2007
 * Created by Allan Crooks
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package com.aelitis.azureus.core.peermanager.messaging.bittorrent.ltep;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.util.Constants;

import com.aelitis.azureus.core.networkmanager.RawMessage;
import com.aelitis.azureus.core.peermanager.messaging.Message;
import com.aelitis.azureus.core.peermanager.messaging.MessageStreamEncoder;
import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTLTMessage;
import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTMessageFactory;

/**
 * @author Allan Crooks
 * 
 */
public class LTMessageEncoder implements MessageStreamEncoder {

    protected final static LogIDs LOGID = LogIDs.PEER;
    private Object log_object;
    private HashMap extension_map;

    public LTMessageEncoder(Object log_object) {
        this.log_object = log_object;
        this.extension_map = null; // Only instantiate it when we need to.
    }

    public RawMessage[] encodeMessage(Message message) {
        if (!(message instanceof LTMessage)) {
            return new RawMessage[] { BTMessageFactory.createBTRawMessage(message) };
        }

        // What type of message is it? LT_handshake messages are always straight forward.
        if (message instanceof LTHandshake) {
            return new RawMessage[] { BTMessageFactory.createBTRawMessage(new BTLTMessage(message, (byte) 0)) };
        }

        // Other message types have to be matched up against the appropriate ID.
        if (extension_map != null) {
            Byte ext_id = (Byte) this.extension_map.get(message.getID());
            if (ext_id != null) {
                // Logger.log(new LogEvent(this.log_object, LOGID, "Converting LT message to BT message, ext id is " + ext_id));
                return new RawMessage[] { BTMessageFactory.createBTRawMessage(new BTLTMessage(message, ext_id.byteValue())) };
            }
        }

        // Anything else means that the client doesn't support that extension.
        // We'll drop the message instead.
        if (Logger.isEnabled())
            Logger.log(new LogEvent(this.log_object, LOGID, "Unable to send LT message of type " + message.getID()
                    + ", not supported by peer - dropping message."));

        return new RawMessage[0];
    }

    public void updateSupportedExtensions(Map map) {
        try {
            Iterator itr = map.entrySet().iterator();
            while (itr.hasNext()) {
                Map.Entry extension = (Map.Entry) itr.next();
                String ext_name;
                Object ext_key = extension.getKey();
                if (ext_key instanceof byte[]) {
                    ext_name = new String((byte[]) ext_key, Constants.DEFAULT_ENCODING);
                } else if (ext_key instanceof String) {
                    ext_name = (String) ext_key;
                } else {
                    throw new RuntimeException("unexpected type for extension name: " + ext_key.getClass());
                }

                int ext_value;
                Object ext_value_obj = extension.getValue();

                if (ext_value_obj instanceof Long) {
                    ext_value = ((Long) extension.getValue()).intValue();
                } else if (ext_value_obj instanceof byte[]) {
                    byte[] ext_value_bytes = (byte[]) ext_value_obj;
                    if (ext_value_bytes.length == 1) {
                        ext_value = (int) ext_value_bytes[0];
                    } else {
                        throw new RuntimeException("extension id byte array format length != 1: " + ext_value_bytes.length);
                    }
                } else {
                    throw new RuntimeException("unsupported extension id type: " + ext_value_obj.getClass().getName());
                }
                if (extension_map == null) {
                    this.extension_map = new HashMap();
                }

                if (ext_value == 0) {
                    this.extension_map.remove(ext_name);
                } else {
                    this.extension_map.put(ext_name, new Byte((byte) ext_value));
                }
            }
        } catch (Exception e) {
            if (Logger.isEnabled())
                Logger.log(new LogEvent(this.log_object, LOGID, "Unable to update LT extension list for peer", e));
        }
    }

    public boolean supportsUTPEX() {
        return (supportsExtension("ut_pex"));
    }

    public boolean supportsUTMetaData() {
        return (supportsExtension("ut_metadata"));
    }

    public boolean supportsExtension(String extension_name) {
        if (extension_map == null) {
            return false;
        }

        Number num = (Number) this.extension_map.get(extension_name);

        return (num != null && num.intValue() != 0);
    }

    public static final int CET_PEX = 1;

    private Map<Integer, CustomExtensionHandler> custom_handlers;

    public void addCustomExtensionHandler(int extension_type, CustomExtensionHandler handler) {
        if (custom_handlers == null) {

            custom_handlers = new HashMap<Integer, LTMessageEncoder.CustomExtensionHandler>();
        }

        custom_handlers.put(extension_type, handler);
    }

    public boolean hasCustomExtensionHandler(int extension_type) {
        if (custom_handlers == null) {

            return (false);
        }

        return (custom_handlers.containsKey(extension_type));
    }

    public Object handleCustomExtension(int extension_type, Object[] args) {
        if (custom_handlers == null) {

            return (null);
        }

        CustomExtensionHandler handler = custom_handlers.get(extension_type);

        if (handler != null) {

            return (handler.handleExtension(args));
        }

        return (null);
    }

    public interface CustomExtensionHandler {
        public Object handleExtension(Object[] args);
    }
}
